--- /dev/null
+# Copyright (C) 2015 Colin Walters <walters@verbum.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+EXTRA_DIST += $(libglnx_srcpath)/README.md $(libglnx_srcpath)/COPYING
+
+libglnx_la_SOURCES = \
++ $(libglnx_srcpath)/glnx-alloca.h \
+ $(libglnx_srcpath)/glnx-backport-autocleanups.h \
+ $(libglnx_srcpath)/glnx-backport-autoptr.h \
+ $(libglnx_srcpath)/glnx-backports.h \
+ $(libglnx_srcpath)/glnx-backports.c \
+ $(libglnx_srcpath)/glnx-local-alloc.h \
+ $(libglnx_srcpath)/glnx-local-alloc.c \
+ $(libglnx_srcpath)/glnx-errors.h \
+ $(libglnx_srcpath)/glnx-errors.c \
+ $(libglnx_srcpath)/glnx-console.h \
+ $(libglnx_srcpath)/glnx-console.c \
+ $(libglnx_srcpath)/glnx-dirfd.h \
+ $(libglnx_srcpath)/glnx-dirfd.c \
+ $(libglnx_srcpath)/glnx-fdio.h \
+ $(libglnx_srcpath)/glnx-fdio.c \
+ $(libglnx_srcpath)/glnx-lockfile.h \
+ $(libglnx_srcpath)/glnx-lockfile.c \
+ $(libglnx_srcpath)/glnx-libcontainer.h \
+ $(libglnx_srcpath)/glnx-libcontainer.c \
+ $(libglnx_srcpath)/glnx-xattrs.h \
+ $(libglnx_srcpath)/glnx-xattrs.c \
+ $(libglnx_srcpath)/glnx-shutil.h \
+ $(libglnx_srcpath)/glnx-shutil.c \
+ $(libglnx_srcpath)/libglnx.h \
+ $(NULL)
+
+libglnx_la_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags)
+libglnx_la_LDFLAGS = -avoid-version -Bsymbolic-functions -export-symbols-regex "^glnx_" -no-undefined -export-dynamic
+libglnx_la_LIBADD = $(libglnx_libs)
--- /dev/null
--- /dev/null
++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
++ *
++ * Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
++ * Boston, MA 02111-1307, USA.
++ */
++
++#pragma once
++
++#include <stdlib.h>
++#include <glib.h>
++
++G_BEGIN_DECLS
++
++/* Taken from https://github.com/systemd/systemd/src/basic/string-util.h
++ * at revision v228-666-gcf6c8c4
++ */
++#define glnx_strjoina(a, ...) \
++ ({ \
++ const char *_appendees_[] = { a, __VA_ARGS__ }; \
++ char *_d_, *_p_; \
++ int _len_ = 0; \
++ unsigned _i_; \
++ for (_i_ = 0; _i_ < G_N_ELEMENTS(_appendees_) && _appendees_[_i_]; _i_++) \
++ _len_ += strlen(_appendees_[_i_]); \
++ _p_ = _d_ = alloca(_len_ + 1); \
++ for (_i_ = 0; _i_ < G_N_ELEMENTS(_appendees_) && _appendees_[_i_]; _i_++) \
++ _p_ = stpcpy(_p_, _appendees_[_i_]); \
++ *_p_ = 0; \
++ _d_; \
++ })
++
++
++G_END_DECLS
--- /dev/null
- static inline void
- g_autoptr_cleanup_gstring_free (GString *string)
- {
- if (string)
- g_string_free (string, TRUE);
- }
-
+/*
+ * Copyright © 2015 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#pragma once
+
+#include <glnx-backport-autoptr.h>
+
+#if !GLIB_CHECK_VERSION(2, 43, 4)
+
+static inline void
+g_autoptr_cleanup_generic_gfree (void *p)
+{
+ void **pp = (void**)p;
+ if (*pp)
+ g_free (*pp);
+}
+
- G_DEFINE_AUTOPTR_CLEANUP_FUNC(GString, g_autoptr_cleanup_gstring_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GAsyncQueue, g_async_queue_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GBookmarkFile, g_bookmark_file_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GBytes, g_bytes_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GChecksum, g_checksum_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GDateTime, g_date_time_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GDir, g_dir_close)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GError, g_error_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GHashTable, g_hash_table_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GHmac, g_hmac_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GIOChannel, g_io_channel_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GKeyFile, g_key_file_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GList, g_list_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GArray, g_array_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GPtrArray, g_ptr_array_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMainContext, g_main_context_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMainLoop, g_main_loop_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GSource, g_source_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMappedFile, g_mapped_file_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMarkupParseContext, g_markup_parse_context_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(gchar, g_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GNode, g_node_destroy)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GOptionContext, g_option_context_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GOptionGroup, g_option_group_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GPatternSpec, g_pattern_spec_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GQueue, g_queue_free)
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GQueue, g_queue_clear)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GRand, g_rand_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GRegex, g_regex_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMatchInfo, g_match_info_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GScanner, g_scanner_destroy)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GSequence, g_sequence_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GSList, g_slist_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GStringChunk, g_string_chunk_free)
+G_DEFINE_AUTO_CLEANUP_FREE_FUNC(GStrv, g_strfreev, NULL)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GThread, g_thread_unref)
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GMutex, g_mutex_clear)
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GCond, g_cond_clear)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GTimer, g_timer_destroy)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GTimeZone, g_time_zone_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GTree, g_tree_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GVariant, g_variant_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GVariantBuilder, g_variant_builder_unref)
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GVariantBuilder, g_variant_builder_clear)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GVariantIter, g_variant_iter_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GVariantDict, g_variant_dict_unref)
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GVariantDict, g_variant_dict_clear)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GVariantType, g_variant_type_free)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GSubprocess, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GSubprocessLauncher, g_object_unref)
+
+/* Add GObject-based types as needed. */
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GCancellable, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GConverter, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GConverterOutputStream, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GDataInputStream, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GFile, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GFileEnumerator, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GFileIOStream, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GFileInfo, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GFileInputStream, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GFileMonitor, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GFileOutputStream, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GInputStream, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMemoryInputStream, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMemoryOutputStream, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GOutputStream, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GSocket, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GSocketAddress, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GTask, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GTlsCertificate, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GTlsDatabase, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GTlsInteraction, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GDBusConnection, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GDBusMessage, g_object_unref)
++G_DEFINE_AUTOPTR_CLEANUP_FUNC(GZlibCompressor, g_object_unref)
++G_DEFINE_AUTOPTR_CLEANUP_FUNC(GZlibDecompressor, g_object_unref)
++
++#endif
++
++#if !GLIB_CHECK_VERSION(2, 45, 8)
++
++static inline void
++g_autoptr_cleanup_gstring_free (GString *string)
++{
++ if (string)
++ g_string_free (string, TRUE);
++}
++
++G_DEFINE_AUTOPTR_CLEANUP_FUNC(GString, g_autoptr_cleanup_gstring_free)
+
+#endif
--- /dev/null
- fwrite (text, 1, textlen - 1, stdout);
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013,2014,2015 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "glnx-console.h"
+
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+
+static char *current_text = NULL;
+static gint current_percent = -1;
+static gboolean locked;
+
+static gboolean
+stdout_is_tty (void)
+{
+ static gsize initialized = 0;
+ static gboolean stdout_is_tty_v;
+
+ if (g_once_init_enter (&initialized))
+ {
+ stdout_is_tty_v = isatty (1);
+ g_once_init_leave (&initialized, 1);
+ }
+
+ return stdout_is_tty_v;
+}
+
+static volatile guint cached_columns = 0;
+static volatile guint cached_lines = 0;
+
+static int
+fd_columns (int fd)
+{
+ struct winsize ws = {};
+
+ if (ioctl (fd, TIOCGWINSZ, &ws) < 0)
+ return -errno;
+
+ if (ws.ws_col <= 0)
+ return -EIO;
+
+ return ws.ws_col;
+}
+
+/**
+ * glnx_console_columns:
+ *
+ * Returns: The number of columns for terminal output
+ */
+guint
+glnx_console_columns (void)
+{
+ if (G_UNLIKELY (cached_columns == 0))
+ {
+ int c;
+
+ c = fd_columns (STDOUT_FILENO);
+
+ if (c <= 0)
+ c = 80;
+
+ if (c > 256)
+ c = 256;
+
+ cached_columns = c;
+ }
+
+ return cached_columns;
+}
+
+static int
+fd_lines (int fd)
+{
+ struct winsize ws = {};
+
+ if (ioctl (fd, TIOCGWINSZ, &ws) < 0)
+ return -errno;
+
+ if (ws.ws_row <= 0)
+ return -EIO;
+
+ return ws.ws_row;
+}
+
+/**
+ * glnx_console_lines:
+ *
+ * Returns: The number of lines for terminal output
+ */
+guint
+glnx_console_lines (void)
+{
+ if (G_UNLIKELY (cached_lines == 0))
+ {
+ int l;
+
+ l = fd_lines (STDOUT_FILENO);
+
+ if (l <= 0)
+ l = 24;
+
+ cached_lines = l;
+ }
+
+ return cached_lines;
+}
+
+static void
+on_sigwinch (int signum)
+{
+ cached_columns = 0;
+ cached_lines = 0;
+}
+
+void
+glnx_console_lock (GLnxConsoleRef *console)
+{
+ static gsize sigwinch_initialized = 0;
+
+ g_return_if_fail (!locked);
+ g_return_if_fail (!console->locked);
+
+ console->is_tty = stdout_is_tty ();
+
+ locked = console->locked = TRUE;
+
+ current_percent = 0;
+
+ if (console->is_tty)
+ {
+ if (g_once_init_enter (&sigwinch_initialized))
+ {
+ signal (SIGWINCH, on_sigwinch);
+ g_once_init_leave (&sigwinch_initialized, 1);
+ }
+
+ { static const char initbuf[] = { '\n', 0x1B, 0x37 };
+ (void) fwrite (initbuf, 1, sizeof (initbuf), stdout);
+ }
+ }
+}
+
+static void
+printpad (const char *padbuf,
+ guint padbuf_len,
+ guint n)
+{
+ const guint d = n / padbuf_len;
+ const guint r = n % padbuf_len;
+ guint i;
+
+ for (i = 0; i < d; i++)
+ fwrite (padbuf, 1, padbuf_len, stdout);
+ fwrite (padbuf, 1, r, stdout);
+}
+
+/**
+ * glnx_console_progress_text_percent:
+ * @text: Show this text before the progress bar
+ * @percentage: An integer in the range of 0 to 100
+ *
+ * On a tty, print to the console @text followed by an ASCII art
+ * progress bar whose percentage is @percentage. If stdout is not a
+ * tty, a more basic line by line change will be printed.
+ *
+ * You must have called glnx_console_lock() before invoking this
+ * function.
+ *
+ */
+void
+glnx_console_progress_text_percent (const char *text,
+ guint percentage)
+{
+ static const char equals[] = "====================";
+ const guint n_equals = sizeof (equals) - 1;
+ static const char spaces[] = " ";
+ const guint n_spaces = sizeof (spaces) - 1;
+ const guint ncolumns = glnx_console_columns ();
+ const guint bar_min = 10;
+ const guint input_textlen = text ? strlen (text) : 0;
+ guint textlen;
+ guint barlen;
+
+ g_return_if_fail (percentage >= 0 && percentage <= 100);
+
+ if (text && !*text)
+ text = NULL;
+
+ if (percentage == current_percent
+ && g_strcmp0 (text, current_text) == 0)
+ return;
+
+ if (!stdout_is_tty ())
+ {
+ if (text)
+ fprintf (stdout, "%s", text);
+ if (percentage != -1)
+ {
+ if (text)
+ fputc (' ', stdout);
+ fprintf (stdout, "%u%%", percentage);
+ }
+ fputc ('\n', stdout);
+ fflush (stdout);
+ return;
+ }
+
+ if (ncolumns < bar_min)
+ return; /* TODO: spinner */
+
+ /* Restore cursor */
+ { const char beginbuf[2] = { 0x1B, 0x38 };
+ (void) fwrite (beginbuf, 1, sizeof (beginbuf), stdout);
+ }
+
+ textlen = MIN (input_textlen, ncolumns - bar_min);
+ barlen = ncolumns - textlen;
+
+ if (textlen > 0)
+ {
- locked = FALSE;
++ fwrite (text, 1, textlen, stdout);
+ fputc (' ', stdout);
+ }
+
+ {
+ const guint nbraces = 2;
+ const guint textpercent_len = 5;
+ const guint bar_internal_len = barlen - nbraces - textpercent_len;
+ const guint eqlen = bar_internal_len * (percentage / 100.0);
+ const guint spacelen = bar_internal_len - eqlen;
+
+ fputc ('[', stdout);
+ printpad (equals, n_equals, eqlen);
+ printpad (spaces, n_spaces, spacelen);
+ fputc (']', stdout);
+ fprintf (stdout, " %3d%%", percentage);
+ }
+
+ { const guint spacelen = ncolumns - textlen - barlen;
+ printpad (spaces, n_spaces, spacelen);
+ }
+
+ fflush (stdout);
+}
+
+/**
+ * glnx_console_unlock:
+ *
+ * Print a newline, and reset all cached console progress state.
+ *
+ * This function does nothing if stdout is not a tty.
+ */
+void
+glnx_console_unlock (GLnxConsoleRef *console)
+{
+ g_return_if_fail (locked);
+ g_return_if_fail (console->locked);
+
+ current_percent = -1;
+ g_clear_pointer (¤t_text, g_free);
+
+ if (console->is_tty)
+ fputc ('\n', stdout);
+
++ locked = console->locked = FALSE;
+}
--- /dev/null
- glnx_console_unlock (p);
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013,2014,2015 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#pragma once
+
+#include <glnx-backport-autocleanups.h>
+
+G_BEGIN_DECLS
+
+struct GLnxConsoleRef {
+ gboolean locked;
+ gboolean is_tty;
+};
+
+typedef struct GLnxConsoleRef GLnxConsoleRef;
+
+void glnx_console_lock (GLnxConsoleRef *ref);
+
+void glnx_console_progress_text_percent (const char *text,
+ guint percentage);
+
+void glnx_console_unlock (GLnxConsoleRef *ref);
+
+guint glnx_console_lines (void);
+
+guint glnx_console_columns (void);
+
+static inline void
+glnx_console_ref_cleanup (GLnxConsoleRef *p)
+{
++ if (p->locked)
++ glnx_console_unlock (p);
+}
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GLnxConsoleRef, glnx_console_ref_cleanup)
+
+G_END_DECLS
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glnx-dirfd.h>
+#include <glnx-errors.h>
+#include <glnx-local-alloc.h>
+
+/**
+ * glnx_opendirat_with_errno:
+ * @dfd: File descriptor for origin directory
+ * @name: Pathname, relative to @dfd
+ * @follow: Whether or not to follow symbolic links
+ *
+ * Use openat() to open a directory, using a standard set of flags.
+ * This function sets errno.
+ */
+int
+glnx_opendirat_with_errno (int dfd,
+ const char *path,
+ gboolean follow)
+{
+ int flags = O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY;
+ if (!follow)
+ flags |= O_NOFOLLOW;
+
+ dfd = glnx_dirfd_canonicalize (dfd);
+
+ return openat (dfd, path, flags);
+}
+
+/**
+ * glnx_opendirat:
+ * @dfd: File descriptor for origin directory
+ * @path: Pathname, relative to @dfd
+ * @follow: Whether or not to follow symbolic links
+ * @error: Error
+ *
+ * Use openat() to open a directory, using a standard set of flags.
+ */
+gboolean
+glnx_opendirat (int dfd,
+ const char *path,
+ gboolean follow,
+ int *out_fd,
+ GError **error)
+{
+ int ret = glnx_opendirat_with_errno (dfd, path, follow);
+ if (ret == -1)
+ {
+ glnx_set_prefix_error_from_errno (error, "%s", "openat");
+ return FALSE;
+ }
+ *out_fd = ret;
+ return TRUE;
+}
+
+struct GLnxRealDirfdIterator
+{
+ gboolean initialized;
+ int fd;
+ DIR *d;
+};
+typedef struct GLnxRealDirfdIterator GLnxRealDirfdIterator;
+
+/**
+ * glnx_dirfd_iterator_init_at:
+ * @dfd: File descriptor, may be AT_FDCWD or -1
+ * @path: Path, may be relative to @df
+ * @follow: If %TRUE and the last component of @path is a symlink, follow it
+ * @out_dfd_iter: (out caller-allocates): A directory iterator, will be initialized
+ * @error: Error
+ *
+ * Initialize @out_dfd_iter from @dfd and @path.
+ */
+gboolean
+glnx_dirfd_iterator_init_at (int dfd,
+ const char *path,
+ gboolean follow,
+ GLnxDirFdIterator *out_dfd_iter,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ glnx_fd_close int fd = -1;
+
+ if (!glnx_opendirat (dfd, path, follow, &fd, error))
+ goto out;
+
+ if (!glnx_dirfd_iterator_init_take_fd (fd, out_dfd_iter, error))
+ goto out;
+ fd = -1; /* Transfer ownership */
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/**
+ * glnx_dirfd_iterator_init_take_fd:
+ * @dfd: File descriptor - ownership is taken
+ * @dfd_iter: A directory iterator
+ * @error: Error
+ *
+ * Steal ownership of @dfd, using it to initialize @dfd_iter for
+ * iteration.
+ */
+gboolean
+glnx_dirfd_iterator_init_take_fd (int dfd,
+ GLnxDirFdIterator *dfd_iter,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GLnxRealDirfdIterator *real_dfd_iter = (GLnxRealDirfdIterator*) dfd_iter;
+ DIR *d = NULL;
+
+ d = fdopendir (dfd);
+ if (!d)
+ {
+ glnx_set_prefix_error_from_errno (error, "%s", "fdopendir");
+ goto out;
+ }
+
+ real_dfd_iter->fd = dfd;
+ real_dfd_iter->d = d;
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/**
+ * glnx_dirfd_iterator_next_dent:
+ * @dfd_iter: A directory iterator
+ * @out_dent: (out) (transfer none): Pointer to dirent; do not free
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Read the next value from @dfd_iter, causing @out_dent to be
+ * updated. If end of stream is reached, @out_dent will be set
+ * to %NULL, and %TRUE will be returned.
+ */
+gboolean
+glnx_dirfd_iterator_next_dent (GLnxDirFdIterator *dfd_iter,
+ struct dirent **out_dent,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GLnxRealDirfdIterator *real_dfd_iter = (GLnxRealDirfdIterator*) dfd_iter;
+
++ g_return_val_if_fail (out_dent, FALSE);
++
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ goto out;
+
+ do
+ {
+ errno = 0;
+ *out_dent = readdir (real_dfd_iter->d);
+ if (*out_dent == NULL && errno != 0)
+ {
+ glnx_set_prefix_error_from_errno (error, "%s", "fdopendir");
+ goto out;
+ }
+ } while (*out_dent &&
+ (strcmp ((*out_dent)->d_name, ".") == 0 ||
+ strcmp ((*out_dent)->d_name, "..") == 0));
+
+ ret = TRUE;
++ out:
++ return ret;
++}
++
++/**
++ * glnx_dirfd_iterator_next_dent_ensure_dtype:
++ * @dfd_iter: A directory iterator
++ * @out_dent: (out) (transfer none): Pointer to dirent; do not free
++ * @cancellable: Cancellable
++ * @error: Error
++ *
++ * A variant of @glnx_dirfd_iterator_next_dent, which will ensure the
++ * `dent->d_type` member is filled in by calling `fstatat`
++ * automatically if the underlying filesystem type sets `DT_UNKNOWN`.
++ */
++gboolean
++glnx_dirfd_iterator_next_dent_ensure_dtype (GLnxDirFdIterator *dfd_iter,
++ struct dirent **out_dent,
++ GCancellable *cancellable,
++ GError **error)
++{
++ gboolean ret = FALSE;
++ struct dirent *ret_dent;
++
++ g_return_val_if_fail (out_dent, FALSE);
++
++ if (!glnx_dirfd_iterator_next_dent (dfd_iter, out_dent, cancellable, error))
++ goto out;
++
++ ret_dent = *out_dent;
++
++ if (ret_dent)
++ {
++
++ if (ret_dent->d_type == DT_UNKNOWN)
++ {
++ struct stat stbuf;
++ if (TEMP_FAILURE_RETRY (fstatat (dfd_iter->fd, ret_dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
++ {
++ glnx_set_error_from_errno (error);
++ goto out;
++ }
++ ret_dent->d_type = IFTODT (stbuf.st_mode);
++ }
++ }
++
++ ret = TRUE;
+ out:
+ return ret;
+}
+
+/**
+ * glnx_dirfd_iterator_clear:
+ * @dfd_iter: Iterator, will be de-initialized
+ *
+ * Unset @dfd_iter, freeing any resources. If @dfd_iter is not
+ * initialized, do nothing.
+ */
+void
+glnx_dirfd_iterator_clear (GLnxDirFdIterator *dfd_iter)
+{
+ GLnxRealDirfdIterator *real_dfd_iter = (GLnxRealDirfdIterator*) dfd_iter;
+ /* fd is owned by dfd_iter */
+ (void) closedir (real_dfd_iter->d);
+ real_dfd_iter->initialized = FALSE;
+}
+
+/**
+ * glnx_fdrel_abspath:
+ * @dfd: Directory fd
+ * @path: Path
+ *
+ * Turn a fd-relative pair into something that can be used for legacy
+ * APIs expecting absolute paths.
+ *
+ * This is Linux specific, and only valid inside this process (unless
+ * you set up the child process to have the exact same fd number, but
+ * don't try that).
+ */
+char *
+glnx_fdrel_abspath (int dfd,
+ const char *path)
+{
+ dfd = glnx_dirfd_canonicalize (dfd);
+ if (dfd == AT_FDCWD)
+ return g_strdup (path);
+ return g_strdup_printf ("/proc/self/fd/%d/%s", dfd, path);
+}
+
+/**
+ * glnx_mkdtempat:
+ * @dfd: Directory fd
+ * @tmpl: (type filename): template directory name
+ * @mode: permissions to create the temporary directory with
+ * @error: Error
+ *
+ * Similar to g_mkdtemp_full, but using openat.
+ */
+gboolean
+glnx_mkdtempat (int dfd,
+ gchar *tmpl,
+ int mode,
+ GError **error)
+{
+ char *XXXXXX;
+ int count;
+ static const char letters[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ static const int NLETTERS = sizeof (letters) - 1;
+ glong value;
+ GTimeVal tv;
+ static int counter = 0;
+
+ g_return_val_if_fail (tmpl != NULL, -1);
+
+ /* find the last occurrence of "XXXXXX" */
+ XXXXXX = g_strrstr (tmpl, "XXXXXX");
+
+ if (!XXXXXX || strncmp (XXXXXX, "XXXXXX", 6))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+ "Invalid temporary directory template '%s'", tmpl);
+ return FALSE;
+ }
+
+ /* Get some more or less random data. */
+ g_get_current_time (&tv);
+ value = (tv.tv_usec ^ tv.tv_sec) + counter++;
+
+ for (count = 0; count < 100; value += 7777, ++count)
+ {
+ glong v = value;
+
+ /* Fill in the random bits. */
+ XXXXXX[0] = letters[v % NLETTERS];
+ v /= NLETTERS;
+ XXXXXX[1] = letters[v % NLETTERS];
+ v /= NLETTERS;
+ XXXXXX[2] = letters[v % NLETTERS];
+ v /= NLETTERS;
+ XXXXXX[3] = letters[v % NLETTERS];
+ v /= NLETTERS;
+ XXXXXX[4] = letters[v % NLETTERS];
+ v /= NLETTERS;
+ XXXXXX[5] = letters[v % NLETTERS];
+
+ if (mkdirat (dfd, tmpl, mode) == -1)
+ {
+ if (errno == EEXIST)
+ continue;
+
+ /* Any other error will apply also to other names we might
+ * try, and there are 2^32 or so of them, so give up now.
+ */
+ glnx_set_prefix_error_from_errno (error, "%s", "mkdirat");
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS,
+ "mkstempat ran out of combinations to try.");
+ return FALSE;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#pragma once
+
+#include <glnx-backport-autocleanups.h>
+#include <limits.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+G_BEGIN_DECLS
+
+/**
+ * glnx_dirfd_canonicalize:
+ * @fd: A directory file descriptor
+ *
+ * It's often convenient in programs to use `-1` for "unassigned fd",
+ * and also because gobject-introspection doesn't support `AT_FDCWD`,
+ * libglnx honors `-1` to mean `AT_FDCWD`. This small inline function
+ * canonicalizes `-1 -> AT_FDCWD`.
+ */
+static inline int
+glnx_dirfd_canonicalize (int fd)
+{
+ if (fd == -1)
+ return AT_FDCWD;
+ return fd;
+}
+
+struct GLnxDirFdIterator {
+ gboolean initialized;
+ int fd;
+ gpointer padding_data[4];
+};
+
+typedef struct GLnxDirFdIterator GLnxDirFdIterator;
+gboolean glnx_dirfd_iterator_init_at (int dfd, const char *path,
+ gboolean follow,
+ GLnxDirFdIterator *dfd_iter, GError **error);
+gboolean glnx_dirfd_iterator_init_take_fd (int dfd, GLnxDirFdIterator *dfd_iter, GError **error);
+gboolean glnx_dirfd_iterator_next_dent (GLnxDirFdIterator *dfd_iter,
+ struct dirent **out_dent,
+ GCancellable *cancellable,
+ GError **error);
++gboolean glnx_dirfd_iterator_next_dent_ensure_dtype (GLnxDirFdIterator *dfd_iter,
++ struct dirent **out_dent,
++ GCancellable *cancellable,
++ GError **error);
+void glnx_dirfd_iterator_clear (GLnxDirFdIterator *dfd_iter);
+
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GLnxDirFdIterator, glnx_dirfd_iterator_clear)
+
+int glnx_opendirat_with_errno (int dfd,
+ const char *path,
+ gboolean follow);
+
+gboolean glnx_opendirat (int dfd,
+ const char *path,
+ gboolean follow,
+ int *out_fd,
+ GError **error);
+
+char *glnx_fdrel_abspath (int dfd,
+ const char *path);
+
+gboolean glnx_mkdtempat (int dfd,
+ gchar *tmpl,
+ int mode,
+ GError **error);
+
+G_END_DECLS
--- /dev/null
- static int loop_write(int fd, const void *buf, size_t nbytes) {
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>.
+ *
+ * Portions derived from systemd:
+ * Copyright 2010 Lennart Poettering
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/ioctl.h>
+#include <sys/sendfile.h>
+#include <errno.h>
+/* See linux.git/fs/btrfs/ioctl.h */
+#define BTRFS_IOCTL_MAGIC 0x94
+#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int)
+
+#include <glnx-fdio.h>
+#include <glnx-dirfd.h>
+#include <glnx-errors.h>
+#include <glnx-xattrs.h>
+#include <glnx-backport-autoptr.h>
+#include <glnx-local-alloc.h>
+
+static guint8*
+glnx_fd_readall_malloc (int fd,
+ gsize *out_len,
+ gboolean nul_terminate,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success = FALSE;
+ const guint maxreadlen = 4096;
+ int res;
+ struct stat stbuf;
+ guint8* buf = NULL;
+ gsize buf_allocated;
+ gsize buf_size = 0;
+ gssize bytes_read;
+
+ do
+ res = fstat (fd, &stbuf);
+ while (G_UNLIKELY (res == -1 && errno == EINTR));
+ if (res == -1)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ if (S_ISREG (stbuf.st_mode) && stbuf.st_size > 0)
+ buf_allocated = stbuf.st_size;
+ else
+ buf_allocated = 16;
+
+ buf = g_malloc (buf_allocated);
+
+ while (TRUE)
+ {
+ gsize readlen = MIN (buf_allocated - buf_size, maxreadlen);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ goto out;
+
+ do
+ bytes_read = read (fd, buf + buf_size, readlen);
+ while (G_UNLIKELY (bytes_read == -1 && errno == EINTR));
+ if (G_UNLIKELY (bytes_read == -1))
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+ if (bytes_read == 0)
+ break;
+
+ buf_size += bytes_read;
+ if (buf_allocated - buf_size < maxreadlen)
+ buf = g_realloc (buf, buf_allocated *= 2);
+ }
+
+ if (nul_terminate)
+ {
+ if (buf_allocated - buf_size == 0)
+ buf = g_realloc (buf, buf_allocated + 1);
+ buf[buf_size] = '\0';
+ }
+
+ success = TRUE;
+ out:
+ if (success)
+ {
+ *out_len = buf_size;
+ return buf;
+ }
+ g_free (buf);
+ return NULL;
+}
+
+/**
+ * glnx_fd_readall_bytes:
+ * @fd: A file descriptor
+ * @cancellable: Cancellable:
+ * @error: Error
+ *
+ * Read all data from file descriptor @fd into a #GBytes. It's
+ * recommended to only use this for small files.
+ *
+ * Returns: (transfer full): A newly allocated #GBytes
+ */
+GBytes *
+glnx_fd_readall_bytes (int fd,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint8 *buf;
+ gsize len;
+
+ buf = glnx_fd_readall_malloc (fd, &len, FALSE, cancellable, error);
+ if (!buf)
+ return NULL;
+
+ return g_bytes_new_take (buf, len);
+}
+
+/**
+ * glnx_fd_readall_utf8:
+ * @fd: A file descriptor
+ * @out_len: (out): Returned length
+ * @cancellable: Cancellable:
+ * @error: Error
+ *
+ * Read all data from file descriptor @fd, validating
+ * the result as UTF-8.
+ *
+ * Returns: (transfer full): A string validated as UTF-8, or %NULL on error.
+ */
+char *
+glnx_fd_readall_utf8 (int fd,
+ gsize *out_len,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success = FALSE;
+ guint8 *buf;
+ gsize len;
+
+ buf = glnx_fd_readall_malloc (fd, &len, TRUE, cancellable, error);
+ if (!buf)
+ goto out;
+
+ if (!g_utf8_validate ((char*)buf, len, NULL))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "Invalid UTF-8");
+ goto out;
+ }
+
+ success = TRUE;
+ out:
+ if (success)
+ {
+ if (out_len)
+ *out_len = len;
+ return (char*)buf;
+ }
+ g_free (buf);
+ return NULL;
+}
+
+/**
+ * glnx_file_get_contents_utf8_at:
+ * @dfd: Directory file descriptor
+ * @subpath: Path relative to @dfd
+ * @out_len: (out) (allow-none): Optional length
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Read the entire contents of the file referred
+ * to by @dfd and @subpath, validate the result as UTF-8.
+ * The length is optionally stored in @out_len.
+ *
+ * Returns: (transfer full): UTF-8 validated text, or %NULL on error
+ */
+char *
+glnx_file_get_contents_utf8_at (int dfd,
+ const char *subpath,
+ gsize *out_len,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success = FALSE;
+ glnx_fd_close int fd = -1;
+ char *buf = NULL;
+ gsize len;
+
+ dfd = glnx_dirfd_canonicalize (dfd);
+
+ do
+ fd = openat (dfd, subpath, O_RDONLY | O_NOCTTY | O_CLOEXEC);
+ while (G_UNLIKELY (fd == -1 && errno == EINTR));
+ if (G_UNLIKELY (fd == -1))
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ buf = glnx_fd_readall_utf8 (fd, &len, cancellable, error);
+ if (G_UNLIKELY(!buf))
+ goto out;
+
+ success = TRUE;
+ out:
+ if (success)
+ {
+ if (out_len)
+ *out_len = len;
+ return buf;
+ }
+ g_free (buf);
+ return NULL;
+}
+
+/**
+ * glnx_readlinkat_malloc:
+ * @dfd: Directory file descriptor
+ * @subpath: Subpath
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Read the value of a symlink into a dynamically
+ * allocated buffer.
+ */
+char *
+glnx_readlinkat_malloc (int dfd,
+ const char *subpath,
+ GCancellable *cancellable,
+ GError **error)
+{
+ size_t l = 100;
+
+ dfd = glnx_dirfd_canonicalize (dfd);
+
+ for (;;)
+ {
+ char *c;
+ ssize_t n;
+
+ c = g_malloc (l);
+ n = TEMP_FAILURE_RETRY (readlinkat (dfd, subpath, c, l-1));
+ if (n < 0)
+ {
+ glnx_set_error_from_errno (error);
+ g_free (c);
+ return FALSE;
+ }
+
+ if ((size_t) n < l-1)
+ {
+ c[n] = 0;
+ return c;
+ }
+
+ g_free (c);
+ l *= 2;
+ }
+
+ g_assert_not_reached ();
+}
+
+static gboolean
+copy_symlink_at (int src_dfd,
+ const char *src_subpath,
+ const struct stat *src_stbuf,
+ int dest_dfd,
+ const char *dest_subpath,
+ GLnxFileCopyFlags copyflags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ g_autofree char *buf = NULL;
+
+ buf = glnx_readlinkat_malloc (src_dfd, src_subpath, cancellable, error);
+ if (!buf)
+ goto out;
+
+ if (TEMP_FAILURE_RETRY (symlinkat (buf, dest_dfd, dest_subpath)) != 0)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ if (!(copyflags & GLNX_FILE_COPY_NOXATTRS))
+ {
+ g_autoptr(GVariant) xattrs = NULL;
+
+ if (!glnx_dfd_name_get_all_xattrs (src_dfd, src_subpath, &xattrs,
+ cancellable, error))
+ goto out;
+
+ if (!glnx_dfd_name_set_all_xattrs (dest_dfd, dest_subpath, xattrs,
+ cancellable, error))
+ goto out;
+ }
+
+ if (TEMP_FAILURE_RETRY (fchownat (dest_dfd, dest_subpath,
+ src_stbuf->st_uid, src_stbuf->st_gid,
+ AT_SYMLINK_NOFOLLOW)) != 0)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+#define COPY_BUFFER_SIZE (16*1024)
+
+/* From systemd */
+
+static int btrfs_reflink(int infd, int outfd) {
+ int r;
+
+ g_return_val_if_fail(infd >= 0, -1);
+ g_return_val_if_fail(outfd >= 0, -1);
+
+ r = ioctl(outfd, BTRFS_IOC_CLONE, infd);
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
+
- r = loop_write(fdt, buf, (size_t) n);
++int glnx_loop_write(int fd, const void *buf, size_t nbytes) {
+ const uint8_t *p = buf;
+
+ g_return_val_if_fail(fd >= 0, -1);
+ g_return_val_if_fail(buf, -1);
+
+ errno = 0;
+
+ while (nbytes > 0) {
+ ssize_t k;
+
+ k = write(fd, p, nbytes);
+ if (k < 0) {
+ if (errno == EINTR)
+ continue;
+
+ return -errno;
+ }
+
+ if (k == 0) /* Can't really happen */
+ return -EIO;
+
+ p += k;
+ nbytes -= k;
+ }
+
+ return 0;
+}
+
+static int copy_bytes(int fdf, int fdt, off_t max_bytes, bool try_reflink) {
+ bool try_sendfile = true;
+ int r;
+
+ g_return_val_if_fail (fdf >= 0, -1);
+ g_return_val_if_fail (fdt >= 0, -1);
+
+ /* Try btrfs reflinks first. */
+ if (try_reflink && max_bytes == (off_t) -1) {
+ r = btrfs_reflink(fdf, fdt);
+ if (r >= 0)
+ return r;
+ }
+
+ for (;;) {
+ size_t m = COPY_BUFFER_SIZE;
+ ssize_t n;
+
+ if (max_bytes != (off_t) -1) {
+
+ if (max_bytes <= 0)
+ return -EFBIG;
+
+ if ((off_t) m > max_bytes)
+ m = (size_t) max_bytes;
+ }
+
+ /* First try sendfile(), unless we already tried */
+ if (try_sendfile) {
+
+ n = sendfile(fdt, fdf, NULL, m);
+ if (n < 0) {
+ if (errno != EINVAL && errno != ENOSYS)
+ return -errno;
+
+ try_sendfile = false;
+ /* use fallback below */
+ } else if (n == 0) /* EOF */
+ break;
+ else if (n > 0)
+ /* Succcess! */
+ goto next;
+ }
+
+ /* As a fallback just copy bits by hand */
+ {
+ char buf[m];
+
+ n = read(fdf, buf, m);
+ if (n < 0)
+ return -errno;
+ if (n == 0) /* EOF */
+ break;
+
- if ((r = loop_write (fd, buf, len)) != 0)
++ r = glnx_loop_write(fdt, buf, (size_t) n);
+ if (r < 0)
+ return r;
+ }
+
+ next:
+ if (max_bytes != (off_t) -1) {
+ g_assert(max_bytes >= n);
+ max_bytes -= n;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * glnx_file_copy_at:
+ * @src_dfd: Source directory fd
+ * @src_subpath: Subpath relative to @src_dfd
+ * @dest_dfd: Target directory fd
+ * @dest_subpath: Destination name
+ * @copyflags: Flags
+ * @cancellable: cancellable
+ * @error: Error
+ *
+ * Perform a full copy of the regular file or
+ * symbolic link from @src_subpath to @dest_subpath.
+ *
+ * If @src_subpath is anything other than a regular
+ * file or symbolic link, an error will be returned.
+ */
+gboolean
+glnx_file_copy_at (int src_dfd,
+ const char *src_subpath,
+ struct stat *src_stbuf,
+ int dest_dfd,
+ const char *dest_subpath,
+ GLnxFileCopyFlags copyflags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int r;
+ int dest_open_flags;
+ struct timespec ts[2];
+ glnx_fd_close int src_fd = -1;
+ glnx_fd_close int dest_fd = -1;
+ struct stat local_stbuf;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ goto out;
+
+ src_dfd = glnx_dirfd_canonicalize (src_dfd);
+ dest_dfd = glnx_dirfd_canonicalize (dest_dfd);
+
+ /* Automatically do stat() if no stat buffer was supplied */
+ if (!src_stbuf)
+ {
+ if (fstatat (src_dfd, src_subpath, &local_stbuf, AT_SYMLINK_NOFOLLOW) != 0)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+ src_stbuf = &local_stbuf;
+ }
+
+ if (S_ISLNK (src_stbuf->st_mode))
+ {
+ return copy_symlink_at (src_dfd, src_subpath, src_stbuf,
+ dest_dfd, dest_subpath,
+ copyflags,
+ cancellable, error);
+ }
+ else if (!S_ISREG (src_stbuf->st_mode))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Cannot copy non-regular/non-symlink file: %s", src_subpath);
+ goto out;
+ }
+
+ src_fd = TEMP_FAILURE_RETRY (openat (src_dfd, src_subpath, O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW));
+ if (src_fd == -1)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ dest_open_flags = O_WRONLY | O_CREAT | O_CLOEXEC | O_NOCTTY;
+ if (!(copyflags & GLNX_FILE_COPY_OVERWRITE))
+ dest_open_flags |= O_EXCL;
+ else
+ dest_open_flags |= O_TRUNC;
+
+ dest_fd = TEMP_FAILURE_RETRY (openat (dest_dfd, dest_subpath, dest_open_flags, src_stbuf->st_mode));
+ if (dest_fd == -1)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ r = copy_bytes (src_fd, dest_fd, (off_t) -1, TRUE);
+ if (r < 0)
+ {
+ errno = -r;
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ if (fchown (dest_fd, src_stbuf->st_uid, src_stbuf->st_gid) != 0)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ if (fchmod (dest_fd, src_stbuf->st_mode & 07777) != 0)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ ts[0] = src_stbuf->st_atim;
+ ts[1] = src_stbuf->st_mtim;
+ (void) futimens (dest_fd, ts);
+
+ if (!(copyflags & GLNX_FILE_COPY_NOXATTRS))
+ {
+ g_autoptr(GVariant) xattrs = NULL;
+
+ if (!glnx_fd_get_all_xattrs (src_fd, &xattrs,
+ cancellable, error))
+ goto out;
+
+ if (!glnx_fd_set_all_xattrs (dest_fd, xattrs,
+ cancellable, error))
+ goto out;
+ }
+
+ if (copyflags & GLNX_FILE_COPY_DATASYNC)
+ {
+ if (fdatasync (dest_fd) < 0)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+ }
+
+ r = close (dest_fd);
+ dest_fd = -1;
+ if (r < 0)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ if (!ret)
+ (void) unlinkat (dest_dfd, dest_subpath, 0);
+ return ret;
+}
+
+/**
+ * glnx_file_replace_contents_at:
+ * @dfd: Directory fd
+ * @subpath: Subpath
+ * @buf: (array len=len) (element-type guint8): File contents
+ * @len: Length (if `-1`, assume @buf is `NUL` terminated)
+ * @flags: Flags
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Create a new file, atomically replacing the contents of @subpath
+ * (relative to @dfd) with @buf. By default, if the file already
+ * existed, fdatasync() will be used before rename() to ensure stable
+ * contents. This and other behavior can be controlled via @flags.
+ *
+ * Note that no metadata from the existing file is preserved, such as
+ * uid/gid or extended attributes. The default mode will be `0666`,
+ * modified by umask.
+ */
+gboolean
+glnx_file_replace_contents_at (int dfd,
+ const char *subpath,
+ const guint8 *buf,
+ gsize len,
+ GLnxFileReplaceFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return glnx_file_replace_contents_with_perms_at (dfd, subpath, buf, len,
+ (mode_t) -1, (uid_t) -1, (gid_t) -1,
+ flags, cancellable, error);
+
+}
+
+/**
+ * glnx_file_replace_contents_with_perms_at:
+ * @dfd: Directory fd
+ * @subpath: Subpath
+ * @buf: (array len=len) (element-type guint8): File contents
+ * @len: Length (if `-1`, assume @buf is `NUL` terminated)
+ * @mode: File mode; if `-1`, use `0666 - umask`
+ * @flags: Flags
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Like glnx_file_replace_contents_at(), but also supports
+ * setting mode, and uid/gid.
+ */
+gboolean
+glnx_file_replace_contents_with_perms_at (int dfd,
+ const char *subpath,
+ const guint8 *buf,
+ gsize len,
+ mode_t mode,
+ uid_t uid,
+ gid_t gid,
+ GLnxFileReplaceFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int r;
+ /* We use the /proc/self trick as there's no mkostemp_at() yet */
+ g_autofree char *tmppath = g_strdup_printf ("/proc/self/fd/%d/.tmpXXXXXX", dfd);
+ glnx_fd_close int fd = -1;
+
+ dfd = glnx_dirfd_canonicalize (dfd);
+
+ if ((fd = g_mkstemp_full (tmppath, O_WRONLY | O_CLOEXEC,
+ mode == (mode_t) -1 ? 0666 : mode)) == -1)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ if (len == -1)
+ len = strlen ((char*)buf);
+
+ /* Note that posix_fallocate does *not* set errno but returns it. */
+ r = posix_fallocate (fd, 0, len);
+ if (r != 0)
+ {
+ errno = r;
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
++ if ((r = glnx_loop_write (fd, buf, len)) != 0)
+ {
+ errno = -r;
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ if (!(flags & GLNX_FILE_REPLACE_NODATASYNC))
+ {
+ struct stat stbuf;
+ gboolean do_sync;
+
+ if (fstatat (dfd, subpath, &stbuf, AT_SYMLINK_NOFOLLOW) != 0)
+ {
+ if (errno != ENOENT)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+ do_sync = (flags & GLNX_FILE_REPLACE_DATASYNC_NEW) > 0;
+ }
+ else
+ do_sync = TRUE;
+
+ if (do_sync)
+ {
+ if (fdatasync (fd) != 0)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+ }
+ }
+
+ if (uid != (uid_t) -1)
+ {
+ if (fchown (fd, uid, gid) != 0)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+ }
+
+ /* If a mode was forced, override umask */
+ if (mode != (mode_t) -1)
+ {
+ if (fchmod (fd, mode) != 0)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+ }
+
+ if (renameat (dfd, tmppath, dfd, subpath) != 0)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#pragma once
+
+#include <glnx-backport-autocleanups.h>
+#include <limits.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/xattr.h>
+/* From systemd/src/shared/util.h */
+/* When we include libgen.h because we need dirname() we immediately
+ * undefine basename() since libgen.h defines it as a macro to the XDG
+ * version which is really broken. */
+#include <libgen.h>
+#undef basename
+
+G_BEGIN_DECLS
+
+/* Irritatingly, g_basename() which is what we want
+ * is deprecated.
+ */
+static inline
+const char *glnx_basename (const char *path)
+{
+ return (basename) (path);
+}
+
+GBytes *
+glnx_fd_readall_bytes (int fd,
+ GCancellable *cancellable,
+ GError **error);
+
+char *
+glnx_fd_readall_utf8 (int fd,
+ gsize *out_len,
+ GCancellable *cancellable,
+ GError **error);
+
+char *
+glnx_file_get_contents_utf8_at (int dfd,
+ const char *subpath,
+ gsize *out_len,
+ GCancellable *cancellable,
+ GError **error);
+
+/**
+ * GLnxFileReplaceFlags:
+ * @GLNX_FILE_REPLACE_DATASYNC_NEW: Call fdatasync() even if the file did not exist
+ * @GLNX_FILE_REPLACE_NODATASYNC: Never call fdatasync()
+ *
+ * Flags controlling file replacement.
+ */
+typedef enum {
+ GLNX_FILE_REPLACE_DATASYNC_NEW = (1 << 0),
+ GLNX_FILE_REPLACE_NODATASYNC = (1 << 1),
+} GLnxFileReplaceFlags;
+
+gboolean
+glnx_file_replace_contents_at (int dfd,
+ const char *subpath,
+ const guint8 *buf,
+ gsize len,
+ GLnxFileReplaceFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean
+glnx_file_replace_contents_with_perms_at (int dfd,
+ const char *subpath,
+ const guint8 *buf,
+ gsize len,
+ mode_t mode,
+ uid_t uid,
+ gid_t gid,
+ GLnxFileReplaceFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+
+char *
+glnx_readlinkat_malloc (int dfd,
+ const char *subpath,
+ GCancellable *cancellable,
+ GError **error);
+
++int
++glnx_loop_write (int fd, const void *buf, size_t nbytes);
++
+typedef enum {
+ GLNX_FILE_COPY_OVERWRITE,
+ GLNX_FILE_COPY_NOXATTRS,
+ GLNX_FILE_COPY_DATASYNC
+} GLnxFileCopyFlags;
+
+gboolean
+glnx_file_copy_at (int src_dfd,
+ const char *src_subpath,
+ struct stat *src_stbuf,
+ int dest_dfd,
+ const char *dest_subpath,
+ GLnxFileCopyFlags copyflags,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012,2013,2015 Colin Walters <walters@verbum.org>.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
++#include <glnx-alloca.h>
+#include <glnx-local-alloc.h>
+#include <glnx-backport-autocleanups.h>
+#include <glnx-backports.h>
+#include <glnx-lockfile.h>
+#include <glnx-errors.h>
+#include <glnx-dirfd.h>
+#include <glnx-shutil.h>
+#include <glnx-xattrs.h>
+#include <glnx-libcontainer.h>
+#include <glnx-console.h>
+#include <glnx-fdio.h>
+
+G_END_DECLS